package com.googlecode.mailnet.simulation;

import com.googlecode.mailnet.netmodel.Node;
import com.googlecode.mailnet.info.INodeInfo;
import com.googlecode.mailnet.info.IMessageListener;
import com.googlecode.mailnet.info.MessageFailureType;
import com.googlecode.mailnet.math.RandomUtil;

import java.util.List;
import java.util.ArrayList;

/**
 * @author Iskander Akishev
 */
class NodeState implements INodeInfo {

	private final SimulationState simulationState;
	final Node node;
	final List<LinkState> incomingLinks, outgoingLinks;
	final List<Message> currentInternalMessages;

	/**
	 * Moment of time when this node will be restored; if it is less than or equal to
	 * {@link SimulationState#currentStepNumber} then the node is not broken now.
	 */
	int breakdownEndTime;

	NodeState(SimulationState simulationState, Node node) {
		this.simulationState = simulationState;
		this.node = node;
		incomingLinks = new ArrayList<LinkState>(node.getIncomingLinks().size());
		outgoingLinks = new ArrayList<LinkState>(node.getOutgoingLinks().size());
		currentInternalMessages = new ArrayList<Message>();
	}

	void simulateProbableFailure() {
		if (breakdownEndTime <= simulationState.currentStepNumber) {
            if (RandomUtil.nextBoolean(node.getFailureProbability())) {
				breakdownEndTime = (simulationState.currentStepNumber +
					RandomUtil.nextInteger(node.getFailureDurationDistribution()));

				for (LinkState outgoingLink : outgoingLinks) {
					outgoingLink.queuesForSending.dropAllMessages(this);
				}
			}
		}
	}

	public boolean isBroken() {
		return (breakdownEndTime > simulationState.currentStepNumber);
	}

	void distributeMessagesOverQueues() {
		if (isBroken()) {
			assert currentInternalMessages.isEmpty();
			return;
		}

		for (Message message : currentInternalMessages) {
			if (message.reachedFinalDestination()) {
				// The message has reached it's final destination
                for (IMessageListener messageListener : simulationState.messageListeners) {
                    messageListener.onMessageDelivered(message);
                }
			} else {
				LinkState linkState = message.getAheadLink();
				assert (incomingLinks.contains(linkState));

				if (!linkState.queuesForSending.offerMessage(message)) {
					// The message didn't fit into the queue
					message.drop(MessageFailureType.QUEUE_OVERFLOW, linkState);
				}
			}
		}
		currentInternalMessages.clear();
	}

	// ========== ILinkInfo implementation ==========

    void clearStatistics() {
	    // does nothing yet
    }

	public int getAttemptedToTransferPerStep() {
		int result = 0;
		for (LinkState link : outgoingLinks) {
			result += link.getAttemptedToTransferPerStep();
		}
		return result;
	}

	public Node getNode() {
		return node;
	}

}
